package com.ruoyi.flow.common.utils;

import com.google.common.collect.Lists;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

/**
 * 文件压缩/解压缩工具类
 *
 * @author wtian
 * @version 1.0
 * @see net.lingala.zip4j.core.ZipFile
 * @since 2017/7/29 13:44
 */
public abstract class ZipUtils {
    private File srcFile;

    public static final ZipUtils on(File srcFile) {
        checkArgument(!FileUtils.isNullOrNotExist(srcFile), "srcFile {} is null or not exist.", srcFile);
        return new SingleZipFileUtils(srcFile);
    }

    public static final ZipUtils on(File[] srcFiles) {
        checkArgument(srcFiles != null && srcFiles.length > 0);
        Arrays.asList(srcFiles).stream().forEach(srcFile -> {
            checkArgument(!FileUtils.isNullOrNotExist(srcFile), "srcFile {} is null or not exist.", srcFile);
        });
        return new MultiZipFileUtils(srcFiles);
    }

    /**
     * 将zip文件解压缩到指定的目录
     *
     * @param destDirectory 压缩目标目录
     * @param password      压缩文件密码。如果没有密码，填写NULL
     */
    public void unzip(File destDirectory, String password) {
        try {
            ZipFile zipFile = new ZipFile(this.srcFile);
            zipFile.setFileNameCharset("UTF-8");
            checkState(zipFile.isValidZipFile(), "压缩文件{}不是合法的压缩文件", this.srcFile);
            FileUtils.createDirectory(destDirectory);
            if (zipFile.isEncrypted()) {
                checkArgument(StringUtils.isNotEmpty(password), "压缩文件是加密文件，必须输入密码");
                zipFile.setPassword(password);
            }
            zipFile.extractAll(destDirectory.getAbsolutePath());
        } catch (ZipException e) {
            e.printStackTrace();
        }
    }

    /**
     * 将zip文件解压缩到与压缩文件相同的目录
     *
     * @param password 压缩文件密码。如果没有密码，填写NULL
     */
    public void unzip(String password) {
        File destDirectory = this.srcFile.getParentFile();
        unzip(destDirectory, password);
    }

    /**
     * 将zip文件解压缩到与压缩文件相同的目录
     */
    public void unzip() {
        File destDirectory = this.srcFile.getParentFile();
        unzip(destDirectory, null);
    }

    /**
     * 使用给定的密码将文件或者文件夹压缩到指定目录
     *
     * @param destDirectory     压缩文件存放目录
     *                          <ul>
     *                          <li>如果是单文件压缩</li>
     *                          </ul>
     * @param zipFileName       压缩后的zip文件名，包含后缀名<code>.zip</code>。
     *                          <ul>
     *                          <li>如果单文件压缩时可以为<code>null</code>，此时采用单文件的文件名</li>
     *                          <li>如果是多文件压缩，不能为NULL</li>
     *                          </ul>
     * @param password          压缩密码。如果为NULL则不添加密码
     * @param containsDirectory 压缩目录时，是否包含目录本身。如果为false，则只将该目录下的文件添加到压缩文件里。
     * @return
     */
    public File zip(File destDirectory, String zipFileName, String password, boolean containsDirectory) {
        String destFilePath = buildZipFilePath(destDirectory, zipFileName);
        ZipParameters parameters = new ZipParameters();
        parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
        parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
        if (StringUtils.isNotEmpty(password)) {
            parameters.setEncryptFiles(true);
            parameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD);
            parameters.setPassword(password);
        }
        try {
            ZipFile zipFile = new ZipFile(destFilePath);
            doZip(zipFile, parameters, password, containsDirectory);
            return new File(destFilePath);
        } catch (ZipException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 基于给定的密码将文件或者目录压缩到相同的目录下。
     *
     * @param password 压缩密码。如果为NULL则不添加密码
     * @return
     */
    public File zip(String password) {
        return zip(null, null, password, true);
    }

    /**
     * 基于给定的密码将文件或者目录压缩到相同的目录下，并且压缩包使用给定的文件名进行命名
     *
     * @param zipFileName 压缩后的zip文件名，包含后缀名<code>.zip</code>
     * @param password    压缩密码。如果为NULL则不添加密码
     * @return
     */
    public File zip(String zipFileName, String password) {
        return zip(null, zipFileName, password, true);
    }

    /**
     * 将文件或者目录压缩到相同目录下
     *
     * @return
     */
    public File zip() {
        return zip(null, null, null, true);
    }

    protected abstract String buildZipFilePath(File destDirectory, String zipFileName);

    /**
     * 抽象方法，实现文件压缩
     *
     * @param zipFile
     * @param parameters
     * @param password
     * @param containsDirectory
     * @throws ZipException
     */
    protected abstract void doZip(ZipFile zipFile, ZipParameters parameters, String password, boolean containsDirectory) throws ZipException;

    /**
     * 单文件压缩工具类
     */
    static class SingleZipFileUtils extends ZipUtils {
        /**
         * 原始文件。需要被压缩的文件/文件夹，或者需要被解压缩的压缩文件
         */

        private boolean isZipFile;
        private boolean isFolder;

        public SingleZipFileUtils(File srcFile) {
            super.srcFile = srcFile;
            if (srcFile.isDirectory()) {
                this.isFolder = true;
            } else {
                String fileName = srcFile.getName();
                String suffix = StringUtils.substringAfterLast(fileName, ".");
                if (StringUtils.equalsIgnoreCase(suffix, "zip")) {
                    this.isZipFile = true;
                }
            }
        }

        @Override
        protected void doZip(ZipFile zipFile, ZipParameters parameters, String password, boolean containsDirectory) throws ZipException {
            if (isFolder) {
                if (!containsDirectory) {
                    File[] subFiles = super.srcFile.listFiles();
                    ArrayList<File> subFileList = Lists.newArrayList(subFiles);
                    zipFile.addFiles(subFileList, parameters);
                } else {
                    zipFile.addFolder(super.srcFile, parameters);
                }
            } else {
                zipFile.addFile(super.srcFile, parameters);
            }
        }

        @Override
        protected String buildZipFilePath(File destDirectory, String zipFileName) {
            //构建文件名
            String fileName = "";
            if (StringUtils.isNotEmpty(zipFileName)) {
                if (StringUtils.endsWithIgnoreCase(zipFileName, ".zip")) {
                    fileName = zipFileName;
                } else {
                    fileName = zipFileName + ".zip";
                }
            } else {
                if (this.isFolder) {
                    fileName = super.srcFile.getName() + ".zip";
                } else {
                    fileName = StringUtils.substringBeforeLast(super.srcFile.getName(), ".") + ".zip";
                }
            }

            //构建目标目录
            if (destDirectory != null) {
                FileUtils.createDirectory(destDirectory);
            } else {//如果目标目录为空，则使用源文件所在目录
                destDirectory = super.srcFile.getParentFile();
            }
            String destDirectoryPath = destDirectory.getAbsolutePath();
            return destDirectoryPath + File.separator + fileName;
        }
    }

    /**
     * 多文件压缩工具类
     */
    static class MultiZipFileUtils extends ZipUtils {
        private File[] srcFiles;

        public MultiZipFileUtils(File[] srcFiles) {
            this.srcFiles = srcFiles;
        }

        @Override
        protected void doZip(ZipFile zipFile, ZipParameters parameters, String password, boolean containsDirectory) throws ZipException {
            ArrayList<File> subFileList = Lists.newArrayList(this.srcFiles);
            zipFile.addFiles(subFileList, parameters);
        }

        /**
         * 构建压缩目标目录和压缩文件名
         *
         * @param destDirectory 目标目录。如果为NULL，则使用临时目录tempdir
         * @param zipFileName   压缩包名。必须填写，不能为NULL
         * @return
         */
        protected String buildZipFilePath(File destDirectory, String zipFileName) {
            //构建文件名
            checkArgument(StringUtils.isNotEmpty(zipFileName), "多文件压缩包名必须填写，不能为空");
            String fileName = "";
            if (StringUtils.isNotEmpty(zipFileName)) {
                if (StringUtils.endsWithIgnoreCase(zipFileName, ".zip")) {
                    fileName = zipFileName;
                } else {
                    fileName = zipFileName + ".zip";
                }
            }

            //构建目标目录
            if (destDirectory != null) {
                FileUtils.createDirectory(destDirectory);
            } else {//如果目标目录为空，则使用源文件所在目录
                destDirectory = SystemUtils.getJavaIoTmpDir();
            }
            String destDirectoryPath = destDirectory.getAbsolutePath();
            return destDirectoryPath + File.separator + fileName;
        }

    }

    public static void main(String[] args) {
        ZipUtils zipUtils = ZipUtils.on(new File("C:\\a.zip"));
        zipUtils.unzip(new File("C:\\unzip"),null);

    }

}
